package it.geosolutions.inversecolormap;

/**
 * This class is responsible for computing efficiently an inverse color map for
 * a given color map.
 * 
 * <p>
 * This algorithm is adapted from the algorithm found in Graphics Gems volume 2
 * by Spencer W. Thomas "Efficient Inverse Color Map Computation".
 * 
 * @author simone
 * 
 */
public final class EfficientInverseColorMapComputation {
	/**
	 * Number of most significant bits we are going to use from the input color
	 * in order to quantize them.
	 */
	protected final int bits;

	protected final int truncationBits;

	protected final int blueQuantizationMask;

	protected final int greenQuantizationMask;

	protected final int redQuantizationMask;

	/**
	 * Forward color map. Is a 3*numcolors array.
	 */
	protected final byte[][] colorMap;

	/**
	 * inverse rgb color map
	 */
	protected final byte[] mapBuf;

	/**
	 * {@link EfficientInverseColorMapComputation} that allows us to specify the
	 * number of bits we are going to save from the quantization.
	 * 
	 * @param rgbColorMap
	 * @param quantizationBits
	 */
	public EfficientInverseColorMapComputation(byte[][] rgbColorMap,
			final int quantizationBits) {
		colorMap = rgbColorMap;
		bits = quantizationBits;
		truncationBits = 8 - bits;
		blueQuantizationMask = (1 << bits) - 1;
		greenQuantizationMask = (blueQuantizationMask << bits);
		redQuantizationMask = (greenQuantizationMask << bits);
		final int maximumQuantizationValue = 1 << bits;
		final int numberOfColors = colorMap[0].length;
		mapBuf = new byte[maximumQuantizationValue * maximumQuantizationValue
				* maximumQuantizationValue];
		final int[] distBuf = new int[maximumQuantizationValue
				* maximumQuantizationValue * maximumQuantizationValue];

		final int x = (1 << truncationBits);
		final int xsqr = x * x;
		final int txsqr = xsqr + xsqr;

		// /////////////////////////////////////////////////////////////////////
		//
		// This code visits every cell in the inverse color map for each
		// representative color.
		//
		// /////////////////////////////////////////////////////////////////////
		for (int i = 0; i < numberOfColors; ++i) {

			// //
			//
			// Get the representative color components
			//
			// //
			final int red = colorMap[0][i] & 0xFF;
			final int green = colorMap[1][i] & 0xFF;
			final int blue = colorMap[2][i] & 0xFF;

			// //
			//
			// Distances are measured to the center of each quantized cell, so
			// x/2 is used as the starting point. This is due to the fact that
			// we quantize by performing right shift.
			//
			// //
			final int x_ = x / 2;
			int rdist = red - x_;
			int gdist = green - x_;
			int bdist = blue - x_;
			// distance
			rdist = rdist * rdist + gdist * gdist + bdist * bdist;

			final int rinc = 2 * (xsqr - (red << truncationBits));
			final int ginc = 2 * (xsqr - (green << truncationBits));
			final int binc = 2 * (xsqr - (blue << truncationBits));

			// //
			//
			// Going to check for all the quantized space
			//
			// //
			for (int r = 0, rxx = rinc, rgbI = 0; r < maximumQuantizationValue; rdist += rxx, ++r, rxx += txsqr) {
				gdist = rdist;
				for (int g = 0, gxx = ginc; g < maximumQuantizationValue; gdist += gxx, ++g, gxx += txsqr) {
					bdist = gdist;
					for (int b = 0, bxx = binc; b < maximumQuantizationValue; bdist += bxx, ++b, ++rgbI, bxx += txsqr) {
						if (i == 0 || distBuf[rgbI] > bdist) {
							distBuf[rgbI] = bdist;
							mapBuf[rgbI] = (byte) i;
						}
					}
				}
			}
		}
	}

	/**
	 * This method is responsible for doing the actual lookup that given an rgb
	 * triple returns the best, taking into account quantization, index in the
	 * forward color map.
	 * 
	 * @param red
	 *            component.
	 * @param green
	 *            component.
	 * @param blue
	 *            component.
	 * @return the best, taking into account quantization, index in the forward
	 *         color map for the provided triple.
	 */
	public int getIndexNearest(int red, int green, int blue) {
		return mapBuf[((red << (2 * bits - truncationBits)) & redQuantizationMask)
				+ ((green << (1 * bits - truncationBits)) & greenQuantizationMask)
				+ ((blue >> (truncationBits)) & blueQuantizationMask)] & 0xFF;
	}

}
